import processing.opengl.*;
import traer.physics.*;

// PARAMETERS
  final int redrawOpacity = 80;
  final color backgroundColor = color(251, 251, 248);

  final int minIndividuals = 6;
  final int maxIndividuals = 12;

  // how many squares to place in the grid.
  // i.e how high the density should be
  final int gridSquaresX = 3;
  final int gridSquaresY = 3;

  final float gridSqrWidth = 400;
  final float gridSqrHeight = 400;
  final float randomMarginal = 0.8;

  final float totalGridW = gridSqrWidth * gridSquaresX;
  final int maxGroups = gridSquaresY * gridSquaresX;
  final float totalGridH = gridSqrHeight * gridSquaresY; 

  final int initialGroups = 1; //(int)(maxGroups * 0.5);

  final float cloudLevel = -1000;

  final float macroZoom = 0.65;

  final float zoomLevelMin = 0.1;
  final float zoomLevelMax = 2.5;
  final float initZoom = 0.33;

  final float initTimeScale = 0.1;
  final float initPanX = 0;
  final float initPanY = -100;

  final float timeScaleMin = 0.05;
  final float timeScaleMax = 2.0;

  // parameters for the fixed random pool
  final int maxFixedNumbers = 2048;
  final float maxFixedValue = 10000.0;

  // image sizes (pixels)
  final int imageSize = 128;
  final int imageSizeHalf = (int)((float)imageSize / 2);

  final int RENDERMODE_MEMES = 1;
  final int RENDERMODE_PRESENTATION = 2;
  final int RENDERMODE_DUAL = 3;

// PARAMETERS END

// MEMBERS
  ParticleSystem ps;
  int numInds = 150;
  int numGroups;

  int renderMode;

  SocialGroup[] groups;

  // the grid on wich the social groups are placed
  int[] grid;

  float zoom, panx, pany;
  float timeScale = 1.0;

  boolean[] keyflags = new boolean[256];

  float zoomStart, zoomStartD;
  float zoomFromX, zoomFromY;

  float travelToX, travelToY, travelToZoom;
  boolean isTraveling = false;

  boolean autoPulling = false;

  Cloud theCloud;
  TextCloud textCloud;

  float[] fixedNumbers = new float[maxFixedNumbers];
  int currentFixed = 0;

  // images
  PImage memeImage = new PImage();
  PImage memeImageMask = new PImage();

  PImage bgFooterImage = new PImage();
  PImage bgFooterImageMask = new PImage();

  PImage bgHeaderImage = new PImage();
  PImage bgHeaderImageMask = new PImage();

  PImage indImage = new PImage();
  PImage indImageMask = new PImage();
  
  PFont smallFont;
  PFont bigFont;

  boolean hiddenCursor = true;
// MEMBERS END

public static void main(String[] args) {
  PApplet.main(new String[] { "--present", "--bgcolor=#F8F8F2", "Main" });
} 

void setup()
{

  // size( 800, 600, OPENGL );
  size(1024, 768, OPENGL);
  // hint(ENABLE_OPENGL_4X_SMOOTH);

  smooth();

  zoom = initZoom;
  timeScale = initTimeScale;
  panx = initPanX;
  pany = initPanY;

  renderMode = RENDERMODE_PRESENTATION;

  frameRate( 50 );
  ellipseMode( CENTER );

  // fill upp the fixed numbers with pseudo random data
  randomSeed(1337);
  print("Creating fixed numbers...");
  for(int i=0; i < maxFixedNumbers; i++) {
    fixedNumbers[i] = random(maxFixedValue);
  }
  println("complete!");

  smallFont = loadFont("font_small.vlw");
  bigFont = loadFont("font_large.vlw");

  ps = new ParticleSystem( 0, 0.5 );

  // load images
  memeImage = loadImage("meme.jpg");
  memeImageMask = loadImage("meme_mask.jpg");
  memeImage.mask(memeImageMask);

  indImage= loadImage("ind.jpg");
  indImageMask = loadImage("ind_mask.jpg");
  indImage.mask(indImageMask);

  bgHeaderImage = loadImage("bg_header.jpg");
  bgHeaderImageMask = loadImage("bg_header_mask.jpg");
  bgHeaderImage.mask(bgHeaderImageMask);

  bgFooterImage = loadImage("bg_footer.jpg");
  bgFooterImageMask = loadImage("bg_footer_mask.jpg");
  bgFooterImage.mask(bgFooterImageMask);


  theCloud = new Cloud();
  textCloud = new TextCloud();

  // create the grid
  grid = new int[maxGroups];

  // create the grid assignment order
  Vector gridAvail = new Vector(maxGroups);
  for(int i=0; i < maxGroups; i++)
    gridAvail.add(new Integer(i));

  // fill up the grid vector with the numbers in random order
  int idx = 0;
  while(gridAvail.size() > 0) {
    Integer pick = (Integer)gridAvail.get(
        (int)random(gridAvail.size()));
    grid[idx++] = pick.intValue();
    gridAvail.remove(pick);
  }

  groups = new SocialGroup[maxGroups];

  for(int i=0; i < initialGroups; i++)
    addGroup();

}

void keyPressed() {
  if((key > 0) && (key < 256)) {
    keyflags[key] = true;
  }

  if(key == '1') renderMode = RENDERMODE_PRESENTATION;
  if(key == '2') renderMode = RENDERMODE_MEMES;
  if(key == '3') {
    renderMode = RENDERMODE_DUAL;
    autoPulling = true;
    timeScale = timeScaleMin;
    panx = 291; pany = 160; zoom = 1.73;
  }

}

boolean isPressed(char key) {
  if(key < 0 || key > 255) return false;
  else return keyflags[key];
}

void keyReleased() {
  if(key > 0 && key < 256) keyflags[key] = false;

  if (key == 'm') {
    int i = (int)fixedRandom(numGroups);

      ((SocialGroup)groups[i]).randomMemeDrop(true);
    /*else {
      ((SocialGroup)groups[i]).randomMemeDrop(false);
      println(" > Dropped normal meme");
    }*/
  }

  if(key == 'a') {
    autoPulling = !autoPulling;
    if(autoPulling)
      println("Autopulling OFF");
    else
      println("Autopulling ON");
  }

  if(key == 'g') addGroup();
  if(key == 'd') 
    travelTo(initPanX, initPanY, initZoom);

  if(key == ' ') {
    if(keyflags['s'])
      textCloud.gotoStart();
    else
      textCloud.nextSlide();
  }
  
  if(key == 'p')
    textCloud.prevSlide();

  if(key == '0')
    timeScale = 0.1;

  if(key == '9')
    timeScale = 0.5;

  if(key == '8')
    timeScale = 0.5;

  if(key == '.') {
    travelTo(groups[0].x(), groups[0].y(), 1.7);
  }

  if(key == 'i') {
    if(hiddenCursor)
      cursor(ARROW);
    else
      noCursor();
  }
  
}

void mouseDragged() {

  if(keyflags['t']) {
    float dy = pmouseY - mouseY;
    timeScale += (float)dy / 100;
    if(timeScale < timeScaleMin) timeScale = timeScaleMin;
    if(timeScale > timeScaleMax) timeScale = timeScaleMax;
  }

  if(keyflags['c']) {
    float dx = pmouseX - mouseX;
    float dy = pmouseY - mouseY;
    panRelative(dx / zoom, dy / zoom);    
  }

  if(keyflags['z']) {

    strokeWeight(1.0);
    stroke(255);
    line(width/2, height/2, mouseX, mouseY);

    float dx = mouseX - ((float)width / 2);
    float dy = mouseY - ((float)height / 2);

    float d = sqrt(dx*dx+dy*dy);

    zoom = zoomStart + (d - zoomStartD) * 0.01 * zoomStart;

    if(zoom < zoomLevelMin) zoom = zoomLevelMin;
    if(zoom > zoomLevelMax) zoom = zoomLevelMax;
  }
}

void mousePressed() {
  if(keyflags['z']) {
    float dx = mouseX - ((float)width / 2);
    float dy = mouseY - ((float)height / 2);

    zoomStartD = sqrt(dx*dx+dy*dy);
    zoomStart = zoom;
  }

}

public void inverseMatrix() {
  translate(panx, pany);
  scale(1/zoom);
  translate(-width/2, -height/2);
}

void draw() {
  ps.advanceTime(timeScale);

  // render bg
  noStroke();
  rectMode(CORNERS);
  fill(backgroundColor);
  rect(0, 0, width, height);

  // hide the cursor when close to the left side
  if(mouseX < 100 && !hiddenCursor) {
    hiddenCursor = true;
    noCursor();
  }

  if (mouseX > 100 && hiddenCursor) {
    hiddenCursor = false;
    cursor(ARROW);
  }

  // travel towards the travelTo pos if travelMode is on
  if(isTraveling) {
    float dx = travelToX - panx;
    float dy = travelToY - pany;
    float dz = travelToZoom - zoom;

    if(abs(dx) < 0.5 && abs(dy) < 0.5 && abs(dz) < 0.01)
      isTraveling = false;
    else {
      dx *= 0.1;
      dy *= 0.1;
      dz *= 0.1;

      panx += dx;
      pany += dy;
      zoom += dz;
    }
  }

  switch(renderMode) {
    case RENDERMODE_DUAL:
    case RENDERMODE_MEMES:
      pushMatrix();
      translate(width/2, height/2);
      scale(zoom);
      translate(-panx, -pany);

      // update groups and draw membranes
      for(int i=0; i < numGroups; i++) {
        groups[i].update();
        groups[i].drawMembrane();
      }

      // draw groups inter connections
      for(int i=0; i < numGroups; i++) {
        groups[i].drawConnections();
      }

      // draw individuals
      for(int i=0; i < numGroups; i++) {
        groups[i].drawIndividuals();
      }

      // draw debug stuff
      for(int i=0; i < numGroups; i++) {
        groups[i].drawDebugInfo();
      }

      theCloud.update();
      theCloud.draw();

      /*
      inverseMatrix();
      fill(63);
      textAlign(LEFT);
      textFont(smallFont, 12);
      text("x:" + panx + ",y=" + pany + 
          ". zoom:" + zoom + 
          " time:" + timeScale, 55, 125);
      */
      popMatrix();
    if(renderMode != RENDERMODE_DUAL) 
      break;

    case RENDERMODE_PRESENTATION:
      pushMatrix();
      translate(width/2, height/2);    
      textCloud.update();
      textCloud.draw();
      popMatrix();
    break;
  }

  float w = bgHeaderImage.width;
  float h = bgHeaderImage.height;

  image(bgHeaderImage, 
      width / 2 - w / 2, 0, 
      w, h);

  image(bgFooterImage, 
      width - bgFooterImage.width, 
      height - bgFooterImage.height, 
      bgFooterImage.width, 
      bgFooterImage.height);
}

SocialGroup getInterGroup(SocialGroup from) {

  // special case of only one group, return itself
  if(numGroups == 1) return from;

  SocialGroup[] otherGroups = new SocialGroup[numGroups - 1];
  int offset = 0;
  // copy all but the 'from' group to the new array
  for(int i=0; i < numGroups - 1; i++) {
    offset += (groups[i] == from ? 1 : 0);
    otherGroups[i] = groups[i + offset];
  }

  // pick a random group from the others
  return otherGroups[(int)fixedRandom(otherGroups.length)];
}

void addGroup() {

  if(numGroups >= maxGroups) {
    println("Warning! Maximum number of groups reached");
    return;
  }
  // take out the next grid position and extract the 
  // x and y values

  float gridx = (float)(grid[numGroups] % gridSquaresX);
  float gridy = (float)(grid[numGroups] / gridSquaresX);

  float ofsx = fixedRandom(((gridSqrWidth * randomMarginal) / 2));
  float ofsy = fixedRandom(((gridSqrHeight * randomMarginal) / 2));

  float px = gridx * gridSqrWidth + gridSqrWidth / 2 + ofsx;
  float py = gridy * gridSqrHeight + + gridSqrHeight / 2 + ofsy;

  float totalGridW = gridSqrWidth * gridSquaresX;
  float totalGridH = gridSqrHeight * gridSquaresY;

  px -= totalGridW/2;
  py -= totalGridH/2;

  SocialGroup spawn = new SocialGroup(
      ps.makeParticle(10, px, py, 0),
      (int)fixedRandom(minIndividuals, maxIndividuals));

  groups[numGroups++] = spawn;
}

float fixedRandom(float bottom, float top) {
  currentFixed = (currentFixed + 1) % maxFixedNumbers;
  float ratio = fixedNumbers[currentFixed] / (float)maxFixedValue;

  return bottom + (top - bottom) * ratio;
}

float fixedRandom(float top) {
  return fixedRandom(0, top);
}

void panRelative(float dx, float dy) {
/*
    for(int i=0; i < numGroups; i++)
      groups[i].moveStatic(-dx, -dy);

    theCloud.moveBy(-dx, -dy);
*/
    panx += dx;
    pany += dy;
}

color mergeColor(color from, color to, float ratio) {
    float red = red(from) * (1 - ratio) + red(to) * ratio;
    float blue = blue(from) * (1 - ratio) + blue(to) * ratio;
    float green = green(from) * (1 - ratio) + green(to) * ratio;
    return color(red, green, blue);
}

void thickLine(float x1, float y1, float x2, float y2) {

  float ox1 = x1; float oy1 = y1;
  float ox2 = x2; float oy2 = y2;

  float dX = ox2-ox1 + 0.0001f;
  float dY = oy2-oy1 + 0.0001f;
  float len = sqrt(dX*dX + dY*dY);

  float rh = (g.strokeWeight/2) / len;

  float dx0 = rh * dY;
  float dy0 = rh * dX;
  float dx1 = rh * dY;
  float dy1 = rh * dX;

  noStroke();
  fill(g.strokeColor);
  beginShape(QUADS);

    vertex(ox1+dx0, oy1-dy0);
    vertex(ox1-dx0, oy1+dy0);

    vertex(ox2-dx1, oy2+dy1);
    vertex(ox2+dx1, oy2-dy1);

  endShape();
}

void travelTo(float x, float y, float z) {
    travelToX = x;
    travelToY = y;
    travelToZoom = z;
    isTraveling = true;
}
